// -------------------------------------------------
// BlinKit - BlinKit Library
// -------------------------------------------------
//   File Name: qjs_window.cpp
// Description: Bindings for Window
//      Author: Ziming Li
//     Created: 2022-03-07
// -------------------------------------------------
// Copyright (C) 2022 MingYang Software Technology.
// -------------------------------------------------

#include "./qjs_bindings.h"

#include "blinkit/blink/renderer/bindings/core/qjs/qjs.h"
#include "blinkit/blink/renderer/bindings/core/qjs/qjs_c_function_list_entries.h"
#include "blinkit/blink/renderer/bindings/core/script_controller.h"
#include "blinkit/blink/renderer/core/dom/document.h"
#include "blinkit/blink/renderer/core/frame/dom_timer.h"
#include "blinkit/blink/renderer/core/frame/local_dom_window.h"
#include "blinkit/blink/renderer/core/frame/Navigator.h"
#include "blinkit/blink/renderer/core/frame/screen.h"
#ifdef BLINKIT_UI_ENABLED
#   include "blinkit/blink/renderer/core/events/custom_event.h"
#endif

/**
 * https://developer.mozilla.org/en-US/docs/Web/API/Window
 */

namespace BlinKit {
namespace qjs {

namespace Strings {
static const char Close[] = "close";
static const char Document[] = "document";
static const char Location[] = "location";
static const char Navigator[] = "navigator";
static const char RequestAnimationFrame[] = "requestAnimationFrame";
static const char SetTimeout[] = "setTimeout";
static const char Window[] = "window";
}

static LocalDOMWindow* GetWindowFromContext(JSContext *ctx)
{
    return ScriptController::From(ctx)->GetWindow();
}

static JSValue AddEventListener(JSContext *ctx, JSValueConst, int argc, JSValueConst *argv)
{
    LocalDOMWindow *window = GetWindowFromContext(ctx);
    return AddEventListenerImpl(ctx, window, argc, argv);
}

static JSValue DispatchEvent(JSContext *ctx, JSValueConst, int argc, JSValueConst *argv)
{
    LocalDOMWindow *window = GetWindowFromContext(ctx);
    return DispatchEventImpl(ctx, window, argc, argv);
}

static JSValue GetDocument(JSContext *ctx, JSValueConst)
{
    ScriptController *scriptController = ScriptController::From(ctx);

    LocalDOMWindow *window = scriptController->GetWindow();
    return scriptController->Wrap(window->document());
}

static JSValue GetLocation(JSContext *ctx, JSValueConst)
{
    ScriptController *scriptController = ScriptController::From(ctx);

    LocalDOMWindow *window = scriptController->GetWindow();
    return scriptController->Wrap(window->location());
}

static JSValue GetNavigator(JSContext *ctx, JSValueConst)
{
    ScriptController *scriptController = ScriptController::From(ctx);

    LocalDOMWindow *window = scriptController->GetWindow();
    return scriptController->Wrap(window->navigator());
}

static JSValue GetWindow(JSContext *ctx, JSValueConst thisVal)
{
    return JS_DupValue(ctx, thisVal);
}

static JSValue RemoveEventListener(JSContext *ctx, JSValueConst, int argc, JSValueConst *argv)
{
    LocalDOMWindow *window = GetWindowFromContext(ctx);
    return RemoveEventListenerImpl(ctx, window, argc, argv);
}

static JSValue RequestAnimationFrame(JSContext *ctx, JSValueConst, int argc, JSValueConst *argv)
{
    LocalDOMWindow *window = GetWindowFromContext(ctx);
    ASSERT(false); // BKTODO:
    return JS_UNDEFINED;
}

static JSValue SetTimeout(JSContext *ctx, JSValueConst thisVal, int argc, JSValueConst *argv)
{
    if (!JS_IsFunction(ctx, argv[0]) && !JS_IsString(argv[0]))
        return JS_ThrowTypeError(ctx, "Invalid function or code");

    LocalDOMWindow *window = GetWindowFromContext(ctx);

    int32_t delay = 0;
    if (argc > 1 && JS_IsNumber(argv[1]))
        JS_ToInt32(ctx, &delay, argv[1]);

    std::unique_ptr<ScheduledAction> action = std::make_unique<ScheduledAction>(ctx, thisVal, argc, argv);
    int32_t timerId = DOMTimer::install(window->executionContext(), action, delay, true);
    return JS_NewInt32(ctx, timerId);
}

#ifdef BLINKIT_CRAWLER_ENABLED
namespace Crawler {

static JSValue SetLocation(JSContext *ctx, JSValueConst thisVal, JSValueConst newLocation)
{
    ASSERT(false); // BKTODO:
    return JS_UNDEFINED;
}

} // namespace Crawler

JSValue CreateWindowPrototypeForCrawler(JSContext *ctx)
{
    static const JSCFunctionListEntry Funcs[] = {
        CFunctionEntry(Strings::AddEventListener, 2, AddEventListener),
        CFunctionEntry(Strings::Close, 0, Crawler::DummyFunction),
        CFunctionEntry(Strings::DispatchEvent, 1, DispatchEvent),
        CGetSetEntry(Strings::Document, GetDocument, nullptr),
        CGetSetEntry(Strings::Location, GetLocation, Crawler::SetLocation),
        CGetSetEntry(Strings::Navigator, GetNavigator, nullptr),
        CFunctionEntry(Strings::RemoveEventListener, 2, RemoveEventListener),
        CFunctionEntry(Strings::RequestAnimationFrame, 1, RequestAnimationFrame),
        CFunctionEntry(Strings::SetTimeout, 1, SetTimeout),
        CGetSetEntry(Strings::Window, GetWindow, nullptr)
    };

    JSValue ret = JS_NewObject(ctx);
    JS_SetPropertyFunctionList(ctx, ret, Funcs, std::size(Funcs));
    return ret;
}
#endif

#ifdef BLINKIT_UI_ENABLED
namespace UI {

static JSValue Close(JSContext *ctx, JSValueConst thisVal, int argc, JSValueConst *argv)
{
    LocalDOMWindow *window = GetWindowFromContext(ctx);
    window->close();
    return JS_UNDEFINED;
}

static JSValue ConstructCustomEvent(JSContext *ctx, JSValueConst thisVal, int argc, JSValueConst *argv)
{
    CustomEventInit init;
    ASSERT(1 == argc); // BKTODO: Process options

    GCRefPtr<CustomEvent> ret = CustomEvent::create(ToAtomicString(ctx, argv[0]), init);
    return ScriptController::From(ctx)->WrapEvent(ret.get());
}

static JSValue GetScreen(JSContext *ctx, JSValueConst thisVal)
{
    ScriptController *scriptController = ScriptController::From(ctx);

    LocalDOMWindow *window = scriptController->GetWindow();
    return scriptController->Wrap(window->screen());
}

} // namespace UI

JSValue CreateWindowPrototypeForUI(JSContext *ctx)
{
    static const JSCFunctionListEntry Funcs[] = {
        CFunctionEntry(Strings::AddEventListener, 2, AddEventListener),
        CFunctionEntry(Strings::Close, 0, UI::Close),
        CFunctionEntry("CustomEvent", 1, UI::ConstructCustomEvent, JS_CFUNC_constructor),
        CFunctionEntry(Strings::DispatchEvent, 1, DispatchEvent),
        CGetSetEntry(Strings::Document, GetDocument, nullptr),
        CGetSetEntry(Strings::Location, GetLocation, nullptr),
        CGetSetEntry(Strings::Navigator, GetNavigator, nullptr),
        CFunctionEntry(Strings::RemoveEventListener, 2, RemoveEventListener),
        CFunctionEntry(Strings::RequestAnimationFrame, 1, RequestAnimationFrame),
        CGetSetEntry("screen", UI::GetScreen, nullptr),
        CFunctionEntry(Strings::SetTimeout, 1, SetTimeout),
        CGetSetEntry(Strings::Window, GetWindow, nullptr)
    };

    JSValue ret = JS_NewObject(ctx);
    JS_SetPropertyFunctionList(ctx, ret, Funcs, std::size(Funcs));
    return ret;
}
#endif

} // namespace qjs
} // namespace BlinKit
